#include "info.h"



//实现

//====================================================================================================================================================================辅助
//获得当前时间戳
long long getCurrentTick(){
    return time(0);
}


/**
 * @brief 时分秒-->秒
 * @param d 天
 * @param h 时
 * @param m 分
 * @param s 秒
 * @return d hh:mm:ss代表的秒数
 */
long long toSeconds(int d,int h,int m,int s){
    long long res = s + 60 * m + 360 * h;
    if(d != 0)res += d*toSeconds(0,24,0,0);
    return res;
}

/**
 * @brief 把Qt字符串内容写入fname文件
 * @param fname 文件路径
 * @param context 内容
 * @return 是否成功
 */
int save_file_string(QString fname,QString context){
    QFile qf(fname);
    if(!qf.open(QIODevice::WriteOnly)){
        QMessageBox::critical(0,"错误","文件保存失败！\n" + fname);
        return 0;
    }
    QTextStream qstream(&qf);
    qstream << context;
    qf.close();
    return 1;
}

/**
 * @brief fix_number 将数值限制在一定范围 并可指定是否弹出警告
 * @param n     要修正的数值
 * @param min   最小值(闭区间)
 * @param max   最大值
 * @param alert 是否警告
 * @return 修正结果
 */
double fix_number(double n,double min,double max,int alert = 0){
    double res = n;
    if(n < min)res = min;
    else if(n > max)res = max;

    if(alert && res != n){
        //数值out of range警告
        QMessageBox::warning(
            0,"数值超出范围",
             QString("当前范围[%1-%2] 您的输入：%3").arg(
                 QString::number(min),
                 QString::number(max),
                 QString::number(n))
             );
    }
    return res;
}

/**
    @brief 输入行节点 设置树的所有列节点为textbox
    @param [in] pt   树指针
    @param [in] head 行节点

*/
void setTextBoxLine_tree(QTreeWidget* pt,QTreeWidgetItem* head){

    if(!head)return;

    if(pt->columnCount()){
        for(int i = 0;i < pt->columnCount();++i){

            QTextEdit* ntb = new QTextEdit();
            ntb->setFixedHeight(30);
            ntb->setAcceptRichText(0);
            pt->setItemWidget(head,i,ntb);

        }

    }

}


/**
    @brief 检查文件是否可以打开
    @param [in] filename 文件名
    @return 0不成功 1成功
*/
int openAble(char* filename){
    FILE* ret = fopen(filename,"r");
    if(!ret)return 0;
    fclose(ret);
    return 1;
}

/**
    @brief 创建一个空文件,存在则不操作
    @param [in] filename 文件名
    @return 0不成功 1成功
*/
int createEmptyFile(char* filename){
    if(openAble(filename))return 0;
    FILE* ret = fopen(filename,"w");
    if(!ret)return 0;
    fclose(ret);
    return 1;
}


/**
 * @brief 获取用户文件路径
 * @param set 设置信息
 * @param name 文件名
 * @param [out] res 结果
 */
void getUserFilename(setting *set,char* name,char* res){
    char filename[2*FILENAME_MAX]={0};
    GETMBSTR(filename,set->userfd);
    FNAMECAT(res,filename,name,"/");
}



/**
 * @brief 打开用户文件
 * @param set 设置信息
 * @param name 文件名
 * @param mode fopen读写参数
 * @return 文件指针
 */
FILE*   openUserFile(setting *set,char* name,char* mode){
    char buf[2*FILENAME_MAX]={0};
    getUserFilename(set,name,buf);
    return fopen(buf,mode);

}

/**
 * @brief 从字符串中得到用户类型
 * @param qstr 目标字符串
 * @return 用户类型
 */
usertype getTypeFromStr(QString wstr){

    if(wstr.compare("学生") == 0)return usertype::Student;
    if(wstr.compare("教师") == 0)return usertype::Teacher;
    if(wstr.compare("管理员") == 0)return usertype::Admin;

    return usertype::not_identified;
}

/**
 * @brief 从字符串中得到用户类型
 * @param qstr 目标字符串
 * @return 用户类型
 */
usertype getTypeFromStr(wchar_t* wstr){

    if(wcscmp(wstr,L"学生") == 0)return usertype::Student;
    if(wcscmp(wstr,L"教师") == 0)return usertype::Teacher;
    if(wcscmp(wstr,L"管理员") == 0)return usertype::Admin;

    return usertype::not_identified;
}


/**
 * @brief 从用户类型中得到字符串
 * @details 转为UTF-8 - Unicode QString
 * @param t 用户类型
 * @return
 */
QString getStrFromType(usertype t){

    switch(t){
    case Student:      
        return QStringLiteral("学生");
    case Teacher:
        return QStringLiteral("教师");
    case Admin:
        return QStringLiteral("管理员");
    case not_identified:
    case guest:
        return QStringLiteral("游客");
    }

}

/**
 * @brief 从用户类型中得到字符串
 * @param t
 * @return
 */
void getStrFromType(usertype t,wchar_t* v){

    switch(t){
    case Student:
        wcscpy(v,L"学生");
        return;
    case Teacher:
        wcscpy(v,L"教师");
        return;
    case Admin:
        wcscpy(v,L"管理员");
        return;
    case guest:
    case not_identified:
        wcscpy(v,L"未知");
        return;
    }

}



/**
 * @brief 将某行内容修改/插入mbstr多字节字符串 或者删除 从0行开始
 *
 * @details 创建临时文件写修改前行内容 再写要修改的内容 最后写修改后行内容
 *
 * @attention 行数说明当start为0 row = 0 代表头一行的操作
 *                   当start大于0 row = 1 代表start的那一行的操作
 * @attention 返回值记得fclose
 *
 * @param filename 文件名
 * @param start 开始位置
 * @param row 行
 * @param mbstr 内容
 * @param mode 见enum类OperationLineFile
 * @return 临时文件指针 0表示失败
 */
FILE* editLineFile(char* filename,long start,int row,char* mbstr,OperationLineFile mode){
    FILE* fp = fopen(filename,"r");
    if(!fp || row < 0 || start < 0)return 0;

    FILE* tmpf = tmpfile();                 //创建一个临时文件
    if(!tmpf){
        fclose(fp);
        return 0;
    }

    int r = 0;                              //一开始是第0行
    char buf[500];

    //setbuf(stdout,0);

    while(r < row && fgets(buf,sizeof(buf),fp)){
        //前
        fprintf(tmpf,"%s",buf);
        //printf("%s",buf);
        if(ftell(fp) >= start)r++;
    }

    if(mode != OperationLineFile::Insert){
        fgets(buf,sizeof(buf),fp);          //再读一行
    }

    if(mode != OperationLineFile::Delete){
        fprintf(tmpf,"%s",mbstr);           //插入内容
        //printf("%s",mbstr);
    }


    while(fgets(buf,sizeof(buf),fp)){
        //后
        fprintf(tmpf,"%s",buf);
        //printf("%s",buf);
    }

    fclose(fp);

    rewind(tmpf);                           //回到文件头部
    return tmpf;
}




/**
 * @brief 按文件指针复制/创建到dest路径
 * @param dest 目标文件路径
 * @param fp 原文件指针
 * @return 复制了多少字节 -1表示失败
 */
long long copyFile_ptr(char* dest,FILE* fp){
    FILE* dp = fopen(dest,"w");
    if(!dp)return -1;

    char t;
    rewind(fp);

    while((t = fgetc(fp)) != EOF){
        fputc(t,dp);
        //putchar(t);
    }

    long long ofs = ftell(fp);          //当前偏移即是文件大小
    fclose(fp);
    fclose(dp);
    return ofs;
}

/**
 * @brief 把路径src的内容复制给dest
 * @param dest 目标文件路径
 * @param src 原文件路径
 * @return 复制了多少字节 -1表示失败
 */
long long copyFile_path(char* dest,char* src){
    FILE* fp = fopen(src,"r");
    if(!fp)return -1;
    return copyFile_ptr(dest,fp);
}

/**
 * @brief 返回keys.csv的一行多字节字符
 * @details 编码转换
 * @param [out] line 输出字符串
 * @param [in] inf 登录信息
 */
void sprintfarg_keyA(char* line,loginInfo* lginf){
    userInfo u = lginf->user;
    QString tp = getStrFromType(lginf->user.type);
    char nm[2*NAMELENMAX] = {0};
    char pm[2*PWSLENMAX] = {0};
    GETMBSTR(nm,u.username);
    GETMBSTR(pm,lginf->user.pws);
    setbuf(stdout,0);
    PRINTARGS_KEYSA(line,u.id,nm,tp.toLocal8Bit().data(),lginf->ntry,pm,lginf->time);
}

/**
 * @brief 根据专业名动态分配
 * @param majname
 * @return
 */
int stu_majupdate(char* majname){

    return 0;
}


/**
 * @brief 从用户记录链表中转为打印的字符串
 * @param head 链表头节点
 * @return ansi的QString 动态存储内存
 */
std::string getStrFromRecL(node* head){
    if(!head || !head->next)return "";
    std::string res;
    node* it = head->next;
    while(it->con){
        if(it->type != nodetype::filename)
            continue;           //跳过不是文件节点的节点
        NODE_FILENAME(fn,it);
        res += fn;
        res += "|";             //记录文件用分隔符
        it = it->next;
    }
    res.pop_back();             //删除最后一个字符
    return res;
}

/**
 * @brief 把学生转为打印的字符串
 * @param stud 学生信息
 * @return ansi字符串
 */
QString getStrFromStu(student* stud){
    if(!stud)return "";
    QString res;


    return res;
}


/**
 * @brief 将std::string分割
 * @param src 原字符串
 * @param delim 分割的字符
 * @param res 结果
 */
void stdStrSplit(const std::string& src,char delim,std::vector<std::string>& res){
    std::istringstream iss(src);
    std::string token;
    res.clear();
    while(std::getline(iss,token,delim)){
        res.push_back(token);
    }
}

/**
 * @brief hashStr
 * @param target
 * @param max(闭)
 * @return
 */
long long hashStr(char* target,long long max,int k = 17){

    //自然溢出法
    long long res = 0;
    long long f = k;

    for(int i = 0;i < strlen(target);++i){
        res += target[i] * f;
        f *= k*k;
    }

    return (res % max + max) % max;
}

/**
 * @brief 用(WINDOWS)系统方式打开文件
 * @param set 设置
 * @param name 文件名
 */
void openUserFile_system(setting *set,char* name){
    char fuser[2*FILENAME_MAX] = {0};
    getUserFilename(set,name,fuser);
    QString c;
    GETQSTRMB(c,fuser);
    QProcess::execute("explorer",{c.replace("/","\\")});

}

//====================================================================================================================================================================设置

/*setting.csv:
  0     [用户信息文件夹路径 = 默认 ./users]
  1     [题目合集文件夹路径 = 默认 ./problems]
*/

/**
    @brief 获得默认配置
    @param [out] set 默认配置
*/
void getSettingDefault(setting* set){
    GETWSTRMB(set->problemfd,FILENAME_PROBLEM);   //复制并转化为宽字符串
    GETWSTRMB(set->userfd,FILENAME_USERINFO);     //读取标准

}


/**
    @brief 从配置文件读取配置
    @param [out] set 配置信息
    @return 0是失败 1是成功
*/
int getSettingInfo(setting* set){
    FILE* fp = fopen(FILENAME_SETTING,"r");
    if(!fp){
        //没找到文件
        getSettingDefault(set);
        saveSettingInfo(set);       //先保存默认的
        return 0;
    }


    //读取setting.csv中的信息
    fgetws(set->userfd,sizeof(set->userfd),fp);
    set->userfd[wcslen(set->userfd)-1] = 0;             //fgets也会把'\n'读入
    fgetws(set->problemfd,sizeof(set->problemfd),fp);

    fclose(fp);
    return 1;
}



/**
    @brief 保存配置文件
    @param [in] set 配置信息
*/
void saveSettingInfo(setting* set){

    FILE* fp = fopen(FILENAME_SETTING,"w");
    if(!fp){
        QMessageBox::critical(0,"没能写入配置文件",strerror(errno));
        return;
    }
    fwprintf(fp,L"%ls\n%ls",set->userfd,set->problemfd);    //写文件

    fclose(fp);

}


/**
    @brief 检查文件完整性
    @param [in] set 配置信息
    @return 0不成功 1成功
*/
int checkSettingInfo(setting* set){
/*检查：
 *程序.exe
 *setting.csv
 *用户信息(文件夹)
 *        学生信息表.csv
 *        教师信息表.csv
 *        统计数据表.csv
 *        密码表.csv
 *        提交记录(文件夹)

 *题目合集(文件夹)

*/
    char filename[256]={0};
    int ret = 1;

    GETMBSTR(filename,set->userfd); //转为char*

    if(mkdir(filename) == -1){
        //未能创建文件夹 --> 说明文件夹已存在或占有
    }else{
        //创建了文件夹 -->说明指定文件夹不存在
        QString c;
        GETQSTRMB(c,filename);

        QMessageBox::warning(0,"未找到用户文件夹 将创建一个空的文件夹",c);
        ret = 0;
    }

    //继续检查其它文件
    QString c;
    char buffer[2*FILENAME_MAX] = {0};
    //设置检查表
    char checklist[5][FILENAME_MAX] =
        { FILENAME_STUDENTS,
          FILENAME_TEACHERS,
          FILENAME_STATISTICS,
          FILENAME_KEY,
          FILENAME_RECORDS
        };


    for(int i = 0;i < 4;++i){
        FNAMECAT(buffer,filename,checklist[i],"/");

        if(!openAble(buffer)){
            GETQSTRMB(c,buffer);
            QMessageBox::warning(0,"未找到文件 创建一个空的",c);
            createEmptyFile(buffer);
            ret = 0;
        }

    }

    FNAMECAT(buffer,filename,checklist[4],"/"); //检查记录文件夹是否存在
    if(mkdir(buffer) == -1){

    }else{
        QString c;

        GETQSTRMB(c,buffer);
        QMessageBox::warning(0,"未找到记录文件夹 将创建一个空的文件夹",c);
        ret = 0;
    }

    GETMBSTR(filename,set->problemfd);
    if(mkdir(filename) == -1){

    }else{
        QString c;
        GETQSTRMB(c,filename);
        QMessageBox::warning(0,"未找到题目文件夹 将创建一个空的文件夹",c);
        ret = 0;
    }
    return ret;
}


/**
 * @brief 返回文件大小
 * @param ret 文件
 * @return 如果返回-1是说明文件不存在
 */
long long sizeFile(FILE* ret){
    if(!ret)return -1;
    fseek(ret,0,SEEK_END);      //最后一个字节的位置
    long long y = ftell(ret);
    rewind(ret);
    return y;
}


/**
 * @brief 返回文件大小
 * @param filename 文件路径
 * @return 如果返回-1是说明文件不存在
 */
long long sizeFile(char* filename){
    FILE* ret = fopen(filename,"r");
    if(!ret)return -1;
    long long y = sizeFile(ret);
    fclose(ret);
    return y;
}


/*
    @brief 用setting设置文件夹和文件
    @param [in] set 配置信息
    @return 0不成功 1成功
*/
void applySetting(setting* set){

    //mkdir();


}


//====================================================================================================================================================================链表



//创建空类型节点 标记首尾
node* createEmptyNode(){
    node* newptr = (node*)malloc(sizeof(node));
    newptr->con = 0;
    newptr->last = newptr->next = 0;
    return newptr;
}


//创建特定类型节点
node* createNewNode(int type){
    node* newptr = (node*)malloc(sizeof(node));
    newptr->con = malloc(type);                         //为数据域分配空间
    newptr->type = type;                                //指明节点类型
    newptr->last = newptr->next = 0;
    return newptr;
}



/**
 *      @brief 移动节点k个单位 可正可负 可自定义停止条件
 *      @param target 原节点
 *      @param k > 0 走next k次;k < 0 走last k次
 *      @param stop(node*)  当stop(node*)满足1时停下或无法继续移动为止
 *      @param [out] sp 实际移动了几步
 *      @return 新的节点
*/
node*   advanceNode(node* target,int k,int (*stop)(node*) = 0,int* sp = 0){
    if(k == 0){
        if(sp)*sp = 0;
        return target;
    }

    int step = fabs(k);
    int sgn = k / step;

    node* cur = target;
    int i = 1;              //移动步数
    for(;i <= step;++i){
        if(stop){
            //指定stop后 该停就停
            if(stop(cur))break;
        }

        if(sgn > 0){
            //向前
            if(!cur->next->con)break;   //无法移动
            cur = cur->next;
        }else{
            //向后
            if(!cur->last->con)break;
            cur = cur->last;
        }

    }


    if(sp){
        *sp = i-1;
    }
    return cur;
}



/**
 * @brief 复制节点内容
 * @param dest 目标
 * @param src 源
 * @return
 */
void copyNode(node* dest,node* src){
    if(src->type == 0 || dest->con == 0 || src->con == 0)return;

    if(dest->type != src->type){
        //类型不一样
        if(dest->con)free(dest->con);
        dest->con = malloc(src->type);
        //分配个新的
    }
    memcpy(dest->con,src->con,src->type);   //复制
}



/**
 *      @brief 插入节点之后
 *      @param type 指定数据域类型
 *      @param tail 尾部节点
 *      @return 新的节点
*/
node* list_insert_back(int type,node* tail){
    if(!tail)return 0;
    //tail --> NULL(A)
    //tail <--> new --> NULL(A)
    node* a = tail->next;
    tail->next = createNewNode(type);
    tail->next->last = tail;
    tail->next->next = a;
    if(a){
        a->last = tail->next;
    }
    return tail->next;
}


/**
 *      @brief 插入节点之前
 *      @param type 指定数据域类型
 *      @param head 头部节点
 *      @return 新的节点
*/
node* list_insert_front(int type,node* head){
    if(!head)return 0;

    //NULL(A) <--> head
    //NULL(A) <--> new <--> head

    node* a = head->last;
    head->last = createNewNode(type);
    head->last->next = head;
    head->last->last = a;
    if(a){
        a->next = head->last;
    }
    return head->last;
}


/**
 *      @brief 删除链表节点 不包括头尾节点
 *      @param type 指定数据域类型
 *      @param target 头部节点
*/
void list_delete(node* target){
    //头尾是只有在链表销毁时被删除 所以只有中间的情况
    if(!target)return;

    //last <--> t <--> next
    //last <--> next

    target->last->next = target->next;
    target->next->last = target->last;

    freeNode(target);

}


void list_clear(node* head){
    if(!head)return;
    if(head->con)return;                                //说明不是空节点
    node* cur = head->next;

    while(cur->con){
        //不是空节点的都是有效数据
        //last --> cur
        freeNode(cur->last);
        cur = cur->next;
    }
    freeNode(cur);


}

void freeNode(node* target){
    if(!target)return;

    switch(target->type){                               //数据域释放
    case stu:{
        //学生

        student* d = (student*)target->con;             //通用节点使用方法
            //->recs是一个文件名链表
        list_clear(d->recfiles);                        //销毁这个链表
        break;
    }
    case filename:
        //文件名
        //数据域不是结构体 是连续的数组 可以直接释放
        break;
    case spe:
        //具体记录
        break;
    case rec:
        //记录
        break;
    }

    free(target->con);                                  //释放数据域指针
    free(target);                                       //释放节点

}


/**
 *      @brief  得到空链表的头尾
 *      @param [out] head头指针的指针
 *      @param [out] tail尾指针的指针
*/
void list_headtail(node** head,node** tail){
    *head = createEmptyNode();
    *tail = createEmptyNode();
    (*head)->next = *tail;
    (*tail)->last = *head;
}


/**
 *      @brief      向尾部追加
 *      @param type 指定数据域类型
 *      @param tail 有效"尾部"节点
 *      @return 新的节点
*/
node* list_push_back(int type,node* tail){
    return list_insert_front(type,tail);          //实际上就是空节点前插
}


/*
 *      @brief      向头部追加
 *      @param type 指定数据域类型
 *      @param head 有效"头部"节点
 *      @return     新的节点
*/
node* list_push_front(int type,node* head){
    return list_insert_back(type,head);
}


//原址排序 返回头尾
//链表归并排序排序（通用）
//@param [in]   head 原链表空头
//@param [in]   tail 原链表空尾
//@param [in]   pfunc:
//		  a < b <==> 升序
//        a > b <==> 降序
//@param [out]  newhead 排序后空头
//@param [out]  newtail 排序后空尾


void list_sort(node* head,node* tail,int (*pfunc)(node* a, node* b),node** newhead,node** newtail){

    node* dlist = _DelEmptyHT(head,tail);           //去除头尾空头
    node* nlist;
    //if(!dlist)return;                             //链表无效

    dlist = _list_sort(dlist,pfunc,newtail);        //归并排序
    nlist =  _AddEmptyHT(dlist,*newtail);           //加空头

    if(newhead){
        //按需赋值
        *newhead = nlist;
    }

    if(newtail){
        if(!dlist){
            *newtail = nlist->next;
        }else{
            *newtail = (*newtail)->next;
        }
    }

}

//从小到大
int cmp_student_ID_less(node* a,node* b){
    NODE_STUDENT(stu1,a);                           //强制转换
    NODE_STUDENT(stu2,b);
    return stu1->ID < stu2->ID;
}


//从大到小
int cmp_student_ID_greater(node* a,node* b){
    NODE_STUDENT(stu1,a);
    NODE_STUDENT(stu2,b);
    return stu1->ID > stu2->ID;
}


//将空头尾删除 替换为 0
node* _DelEmptyHT(node* head,node* tail){
    if(head == 0 || tail == 0)return 0;

    if(head->next == tail){     //这时空节点不需要特殊free处理
        free(head);
        free(tail);
        return 0;
    }
    node* nhead = head->next;
    node* ntail = tail->last;

    free(nhead->last);
    nhead->last = 0;
    free(ntail->next);
    ntail->next = 0;

    return nhead;
}

//加上头尾空节点
node* _AddEmptyHT(node* head,node* tail){


    node *n1,*n2;
    n1 = createEmptyNode();
    n2 = createEmptyNode();

    if(head == 0 || tail == 0){
        //只要有一个是空的都没法接
        n1->next = n2;
        n2->last = n1;
        return n1;
    }

    head->last = n1;
    head->last->next = head;
    tail->next = n2;
    tail->next->last = tail;
    return head->last;
}


node* _list_sort(node* L,int (*pfunc)(node* a, node* b),node** tail) {
    //归并排序 快慢指针法实现
    //如果没有元素或只有一个元素则不需要排序
    if (L == 0){
        if(tail)*tail = 0;
        return 0;
    }

    if(L->next == 0){
        if(tail)*tail = L;
        return L;
    }

    node* slow = L;	//慢
    node* fast = L; //快
    node *h1, *h2,*p;	//合并头部
    node t;			//临时头部


    while (fast->next && fast->next->next) {
        fast = fast->next->next;
        slow = slow->next;
    }
    //找到中点 进行左右分割
    //L --- slow --- fast --> NULL
    //L --- slow --> NULL,slow+1 --- fast --> NULL
    h1 = L, h2 = slow->next;
    slow->next = 0;
    h1 = _list_sort(L, pfunc,0);	//找到左头
    h2 = _list_sort(h2, pfunc,0);	//找到右头
    p = &t;


    while (h1 && h2) {
        //"小"的先放 跟在t链表后
        if (pfunc(h1, h2)) {
            p->next = h1;h1->last = p;
            h1 = h1->next;
        }
        else {
            p->next = h2; h2->last = p;
            h2 = h2->next;
        }
        p = p->next;
    }

    if (h1 != 0) {
        p->next = h1; h1->last = p;
    }
    if (h2 != 0) {
        p->next = h2; h2->last = p;
    }

    t.next->last = 0;	//设置头部
    if (tail) {
        //如果需要尾节点
        while (p->next) {
            p = p->next;
        }
        *tail = p;
    }

    return t.next;

}


/**
 * @brief 查找head之后第一个指定条件的节点
 * @param stop 当stop(node*)满足1时停下 0代表无法继续移动为止
 * @param [out] sp 走了几步 (不含空头节点)
*/
node* list_find_next(node* head,int (*stop)(node*),int* sp = 0){
    if(head->con == 0 && head->last == 0)
        head = head->next;                          //如果是头空节点要移动到有效节点

    return advanceNode(head,INT32_MAX,stop,sp);
}


/**
 * @brief 查找head之后指定条件的所有节点 形成新链表
 * @param stop 当stop(node*)满足1时停下 0代表无法继续移动为止
 * @param list_result 新链表
 */
void list_find_list(node* head,int (*stop)(node*),node** list_result){
    if(!list_result)return;
    if(head->con == 0 && head->last == 0)
        head = head->next;

    node* cur = head;
    node *h,*t,*newp;
    list_headtail(&h,&t);

    while(cur->con){
        cur = list_find_next(cur,stop);         //寻找下一个
        newp = list_push_back(cur->type,t);     //插入新链表
        copyNode(newp,cur);                     //复制内容
        cur = cur->next;
    }

    *list_result = h;
}


//下为排序函数合集 u表示升序 中文不排序
int liststu_ID_u(node* a,node* b){
    NODE_STUDENT(sa,a);
    NODE_STUDENT(sb,b);
    return sa->ID < sb->ID;
}

int liststu_cls_u(node* a,node* b){
    NODE_STUDENT(sa,a);
    NODE_STUDENT(sb,b);
    if(sa->cls == sb->cls)return liststu_name_u(a,b);
    return sa->cls < sb->cls;
}

int liststu_grd_u(node* a,node* b){
    NODE_STUDENT(sa,a);
    NODE_STUDENT(sb,b);
    if(sa->grd == sb->grd)return liststu_name_u(a,b);
    return sa->grd < sb->grd;
}

int liststu_year_u(node* a,node* b){
    NODE_STUDENT(sa,a);
    NODE_STUDENT(sb,b);
    if(sa->year == sb->year)return liststu_name_u(a,b);
    return sa->year < sb->year;
}

int liststu_name_u(node* a,node* b){
    NODE_STUDENT(sa,a);
    NODE_STUDENT(sb,b);
    QString q1,q2;
    GETQSTRMB(q1,sa->name);
    GETQSTRMB(q2,sb->name);
    return QString::compare(q1,q2) < 0;
}

int liststu_major_u(node* a,node* b){
    NODE_STUDENT(sa,a);
    NODE_STUDENT(sb,b);
    QString q1,q2;
    GETQSTRMB(q1,sa->major);
    GETQSTRMB(q2,sa->major);
    return QString::compare(q1,q2) < 0;
}


int liststu_ID_d(node* a,node* b){
    NODE_STUDENT(sa,a);
    NODE_STUDENT(sb,b);
    return sa->ID > sb->ID;
}

int liststu_cls_d(node* a,node* b){
    NODE_STUDENT(sa,a);
    NODE_STUDENT(sb,b);
    if(sa->cls == sb->cls)return liststu_name_u(a,b);
    return sa->cls > sb->cls;
}

int liststu_grd_d(node* a,node* b){
    NODE_STUDENT(sa,a);
    NODE_STUDENT(sb,b);
    if(sa->grd == sb->grd)return liststu_name_u(a,b);
    return sa->grd > sb->grd;
}

int liststu_year_d(node* a,node* b){
    NODE_STUDENT(sa,a);
    NODE_STUDENT(sb,b);
    if(sa->year == sb->year)return liststu_name_u(a,b);
    return sa->year > sb->year;
}

int liststu_name_d(node* a,node* b){
    NODE_STUDENT(sa,a);
    NODE_STUDENT(sb,b);
    QString q1,q2;
    GETQSTRMB(q1,sa->name);
    GETQSTRMB(q2,sb->name);
    return QString::compare(q1,q2) > 0;
}

int liststu_major_d(node* a,node* b){
    NODE_STUDENT(sa,a);
    NODE_STUDENT(sb,b);
    QString q1,q2;
    GETQSTRMB(q1,sa->major);
    GETQSTRMB(q2,sa->major);
    return QString::compare(q1,q2) > 0;
}

